Part 48: The Wardialer
Part 48 - The Wardialer=== Trash World Inbox ===
Quackles posted:
Anyway, time for me to take a stab at my version. This version actually has a few funny stories behind it.
When I first solved this puzzle, I didn't figure out the lock, so my version tested the codes 111, 112, 113, 114, 115... and so on. I ended up with a whopping 20k cycle count. This is part of why the cycles histogram for this puzzle is so skewed.
Later, before the LP began, I went back and figured the lock out. That got me to 795/83/100.
Now, I've adopted a number of optimizations from CO2's post, including loop unrolling (albeit for the lock in my case), and hardcoding the expected length of the file.We start by unrolling the lock loop to copy 111 through 999 into the lock and adding the result to T. T has the completed combination at the end of the lock.code:
;FIRST TRY TO BREAK LOCK ;IT'S LIKE A MASTERMIND GAME - ;CORRECT DIGITS ARE HELD GRAB 300 LINK 800 MARK LOCKLOOP @REP 9 COPY @{111,111} #LOCK ;111, 222, 333... ADDI T #LOCK T @END COPY T #LOCK
This block of code tests each of the files in 801-804 to see if it's about Project Ogre. If so, we jump to 80#COPY (there are four of these labels), which sets the link ID before jumping to the label COPY, which copies the file.code:
;NOW WE SEARCH LINK 800 COPY F X ;'PROJECT OGRE' DROP @REP 4 LINK 80@{1,1} ;801, 802, 803, 804 GRAB 200 TEST F = X TJMP 80@{1,1}COPY ;IT WORKS WITH LABELS TOO! DROP LINK -1 @END COPY 0 T GRAB 300 JUMP LOCKLOOP @REP 4 MARK 80@{1,1}COPY COPY 80@{1,1} T ;SET LINK ID (T) TO 801, 802... JUMP COPY @END
Finally, we're ready to do the actual copying. We replace the first two values of file 300 (the source file) with the file pointer (4) and link ID. Then, the copy loop has us go into the link, jump to the chosen point in the file, and copy two values into X and T. We return to file 300 and use SEEK F to pick up where we left off. (As CO2 noted above, this actually jumps us to location F+1, but there's a fix for that later. As the last part of the loop, we update the file pointer, read the link ID again, and dive back in.code:
MARK COPY ;PERFORM SOME SETUP ;AS WE CAN'T HOLD 3 VARS AT ONCE DROP LINK -1 GRAB 300 COPY 4 X ;FILE PTR COPY X F COPY T F ;LINK ID DROP MARK COPYLOOP LINK T GRAB 200 SEEK X COPY F T COPY F X DROP LINK -1 GRAB 300 SEEK F COPY T F COPY X F SEEK -9999 ADDI F 2 X ;FILE PTR TEST X > 42 TJMP BAIL COPY F T ;LINK ID SEEK -2 COPY X F DROP JUMP COPYLOOP
Once the file pointer is over 42, we jump to the end code:We grab the first two values from file 200 and write over where we stored the file pointer and link ID in our file. Then we return home.code:
MARK BAIL ;REPLACE ORIGINAL TWO FILE VALUES SEEK -9999 SEEK 1 COPY F X DROP LINK X GRAB 200 COPY F X COPY F T DROP LINK -1 GRAB 300 COPY X F COPY T F SEEK 2 VOID F LINK -1 LINK -1 LINK -1
506/112/60. Not bad, all things being equal.
=== The Wardialer ===
It is time to create that wardialer we heard about in the chat.
OST: Network Exploration
The assignment:
- File 300 contains a phone number where one to three of the digits have been replaced with a placeholder (X). Using your modem, dial all possible phone numbers that could be generated by replacing the placeholders with the digits 0 through 9. When you discover a valid phone number that connects to another host, append it to file 301.
- Note that the phone numbers must be dialed and appended in a specific order such that a placeholder is only incremented when the placeholder to the right cycles through all possible values from 0 to 9 and is reset to 0.
- For more information see "Hacker Skills: Model Control at the Direct Level" in the second issue of the zine.
Another modem level. The second point of the assignment is just a complicated way to say the results should be ordered from low to high.
The files in the remote hosts contain some other phone numbers. They don't match the partial number in file 300 so I have no use for them. There's also some hardware registers out there I don't care about for this assignment.
For this first test, the partial number is 94-0-177X-X96X. To get the phone numbers in order, I should first dial 94-0-1770-0960, then 94-0-1770-0961 and so on.
The easiest way to handle this is if I can just update those placeholders in-place. However, I need to keep track which values are placeholders.
I'm thinking I can do this by choosing alternate numbers that are outside the normal range and that can easily be converted back to the actual ones.
I think I'll just subtract 10 from every number, so the digit 0 becomes -10, and the digit 9 becomes -1. To dial it, I'll just have to add 10 again.
code:
GRAB 300
LINK 800
; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP DIAL
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP
code:
MARK DIAL
SEEK -9999
@REP 11
MODI F 10 #DIAL
@END
As a side note, it turns out that there's no single definition for modulo operations on negative numbers. Is -4 / 10 equal to -1 remainder 6 or to 0 remainder -4? Different programming languages disagree on this so this code might fail if run on anything else than an EXA. In this case, MODI -4 10 is equal to 6, which is what I need.
Now, what's next? Creating a REPL to go check if there's a connection? Sure, we could do that, but it's always good to pay close attention to what's happening in the system.
In this case, I'm coding for the NETronics NET40 modem, not the TEC EXA-Blaster from the previous assignments. It's almost identical but it has one significant difference.
There's a number visible on this #DIAL register. That means it isn't write-only. When you read it, it returns 1 when the modem is connected, 0 otherwise. That means the entire connection check is just a single TEST of the #DIAL register.
code:
COPY #DIAL T
FJMP NEXTNUMBER
; TODO
;HANGUP
COPY -1 #DIAL
MARK NEXTNUMBER
code:
MARK NEXTNUMBER
; ALREADY AT EOF
SEEK -1
TEST F < 0
SEEK -1
FJMP NEXTNUMBER
ADDI F 1 T
SEEK -1
FJMP ROLLOVER
COPY T F
JUMP DIAL
MARK ROLLOVER
COPY -10 F
SEEK -1
JUMP NEXTNUMBER
In most cases, it will just add 1 to the value and write that back to the file. The jump to ROLLOVER only happens if T is 0 - that is, if I just tried all 10 digits. In that case, I need to write -10 back to that value so the code keeps recognizing it as a placeholder. Then I skip over it, and find the next placeholder in the file.
Next round, the first placeholder is -10 again, it becomes -9, and so on, until it rolls back round to 0, the ROLLOVER is triggered and the next digit is incremented. It works recursively for however many placeholders there are.
Once all numbers have been tried, it will rollover past the final placeholder in the file, and get in an infinite loop as it keeps SEEKing to the start forever.
Let's handle that.
There's no direct way to test if the cursor is at the start of the file. You could test if the digit at the current location is the same as the one at the start but of course digits can occur multiple times. Since the phone numbers are always 11 digits I decided to use a simple counter.
Just after dialing, 11 is stored to X. The NEXTNUMBER loop is amended:
code:
MARK NEXTNUMBER
TEST X = 0
TJMP END
; ALREADY AT EOF
SEEK -1
TEST F < 0
SEEK -1
SUBI X 1 X
FJMP NEXTNUMBER
With that done, it's time to copy the valid numbers.
I can replace the TODO from earlier with a simple snippet that copies the actual digits over M:
code:
SEEK -9999
@REP 11
MODI F 10 M
@END
Fuck nazis. Anyway, XB can just read M 88 times, because for every test there are 8 valid numbers, so that's 8 times 11 digits.
This is my first working solution.
With 147 lines, the fully unrolled XB just barely fits. And with 65K cycles, it takes an age and a half to even verify that this is a working solution.
So, I'm at 65154/147/1. Top percentiles are 20.7K, 47 and 1. Let's see if I can improve some things.
First of all, for a quick reduction of the size, I'll just roll up loops.
Simply replacing all three @REPs with a COPY number T followed by a loop that has a SUBI T 1 T and a TJMP reduces the size to 52, but the cycle count becomes 88338.
If XB KILLs XA after it got all the phone numbers, I can remove XA's counter and completion check in NEXTNUMBER. This brings the size down to 51.
code:
DROP
LINK 800
KILL
GRAB 300
WIPE
I also have a couple non-conditional JUMP statements. Perhaps I can get rid of one by reordering the code.
code:
;XA
GRAB 300
LINK 800
; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP DIAL
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP
MARK NEXTNUMBER
; ALREADY AT EOF
SEEK -1
TEST F < 0
SEEK -1
FJMP NEXTNUMBER
ADDI F 1 T
SEEK -1
FJMP ROLLOVER
COPY T F
MARK DIAL
SEEK -9999
COPY 11 T
MARK DIALLP
MODI F 10 #DIAL
SUBI T 1 T
TJMP DIALLP
COPY #DIAL T
FJMP NEXTNUMBER
SEEK -9999
COPY 11 T
MARK COPYLOOP
MODI F 10 M
SUBI T 1 T
TJMP COPYLOOP
;HANGUP
COPY -1 #DIAL
MARK ROLLOVER
COPY -10 F
SEEK -1
JUMP NEXTNUMBER
;XB
GRAB 301
COPY 88 T
MARK LP
COPY M F
SUBI T 1 T
TJMP LP
DROP
LINK 800
KILL
GRAB 300
WIPE
And for the final size reduction, I can save a line by replacing the COPY #DIAL T with MULI #DIAL 11 T. The FJMP will still act the same, but the COPY 11 T just below that is no longer necessary. 66942/49/3. Only 2 lines away from the top percentile score.
Except for rolling up the loops, all the changes I just did not only reduced the size, but also made the solution run faster.
Keeping the speed improvements and unrolling all three loops again, I'm at 43904/145/3. Much better than what I had but still far from the top percentile.
I have a couple ideas on how to get there. For instance, it would be much faster to update the placeholders from left to right, but that would require sorting in XB. Or, you could send multiple digits over M with one message, because 4 digits fit in a number. But that would require multiplying the digits by factors of 10, and that's difficult when I also need the MODI trick to deal with the placeholders. It might not really be faster.
The third option and the one which I think is most likely to work, is parallelism. You could have one EXA dialing while another is updating the number. Of course you can't do that on the same number so you'd have to make a copy. Perhaps one EXA could try all numbers where the last placeholder is odd, another where all of them are even.
And the final option, of course, is to cheese it by building special cases for the slowest tests.
I don't like to cheese the tests if I don't have to, so let's start with the third option. Here is the entire code:
code:
;XA LOCAL
GRAB 300
LINK 800
REPL ODD
; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP COPY
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP
MARK ODD
MAKE
@REP 11
COPY M F
@END
COPY 0 M
MARK MAKELASTODD
SEEK -1
TEST F = -10
SEEK -1
FJMP MAKELASTODD
COPY -9 F
JUMP DIAL
MARK COPY
SEEK -9999
@REP 11
COPY F M
@END
MARK DIAL
VOID M
SEEK -9999
@REP 11
MODI F 10 #DIAL
@END
MULI #DIAL 11 T
FJMP RELEASE
SEEK -9999
MODE
@REP 11
MODI F 10 M
@END
MODE
;HANGUP
COPY -1 #DIAL
MARK RELEASE
COPY 0 M
MARK NEXTNUMBER
SEEK -1
TEST F < 0
SEEK -1
FJMP NEXTNUMBER
ADDI F 2 T
SEEK -1
SUBI T X T
FJMP ROLLOVER
COPY T F
TEST T = 1
TJMP ODDROLLOVER
COPY 0 X
JUMP DIAL
MARK ROLLOVER
COPY 1 X
COPY -10 F
SEEK -1
JUMP NEXTNUMBER
MARK ODDROLLOVER
COPY 1 X
SEEK -1
COPY -9 F
SEEK -1
JUMP NEXTNUMBER
;XB GLOBAL
GRAB 301
COPY 8 T
MARK LP
@REP 11
COPY M F
@END
SUBI T 1 T
TJMP LP
DROP
LINK 800
KILL
KILL
GRAB 300
WIPE
GRAB 400
WIPE
The communications in LOCAL mode are purely to prevent both EXAs from dialing at the same time. As soon as one is done dialing, it releases the other with an M message so it can start dialing the next number. I added MODE instructions around the copy-to-XB code so that part is done in GLOBAL mode.
I also needed some special code in the NEXTNUMBER. It keeps X set to zero unless a rollover is triggered, in which case X becomes 1. The code updates each placeholder value by adding (2 - X). That way, it tries every 2nd number for the last placeholder, but every single number for the others.
In the old logic, once the value was increased all the way up to 0 this triggered the ROLLOVER through an FJMP. For the odd EXA, this won't work because the value will jump from -1 to 1, both considered true. I added special handling for this case through the ODDROLLOVER.
XB is mostly the same. I had to partially roll up the loop to make space for the extra code, and of course kill the additional EXA and its file.
38579/127/4. An improvement but not as much as I'd hoped. It might be slightly faster to release the other XA before sending data over GLOBAL M, but that leads to an issue where the odd EXA gets stuck once the even one tries all numbers and gets in the infinite loop, never reaching the VOID M.
There are specific tests that run very slowly. It's because all the placeholders are near the start of the phone number, and every time it seeks through the entire file from end to start.
I can partially solve this by storing the location of the last placeholder in the file, like so:
code:
MARK FIND
SEEK -1
TEST F = -10
ADDI X 1 X
SEEK -1
FJMP FIND
SEEK 9999
SUBI 0 X F
COPY 0 X
22549/139/4. Getting much closer to the top percentile. The slowest test is now one that has the last placeholder near the end of the file, but two others near the start.
That could possibly be solved by keeping track of the location of multiple placeholders, but that's a lot more difficult than what I just did. Some tests only have one placeholder at all, so you'd have to handle that. It'd also require some additional place to even store it, where it could be accessed quickly. And I'm running out of space for lines of code. So, I'll leave further improvements to you. The full code is just below in the appendix if you want to give it a go.
Next time, we help selenium_wolf.
=== Appendix ===
Wardialer 22549/139/4
code:
;XA LOCAL
GRAB 300
LINK 800
REPL ODD
; FIX PLACEHOLDERS
MARK FIXLP
TEST EOF
TJMP FIND
TEST F < 9999
TJMP FIXLP
SEEK -1
COPY -10 F
JUMP FIXLP
MARK ODD
MAKE
@REP 12
COPY M F
@END
COPY 0 M
SEEK -1
MARK MAKELASTODD
SEEK -1
TEST F = -10
SEEK -1
FJMP MAKELASTODD
COPY -9 F
JUMP DIAL
MARK FIND
SEEK -1
TEST F = -10
ADDI X 1 X
SEEK -1
FJMP FIND
SEEK 9999
SUBI 0 X F
COPY 0 X
SEEK -9999
@REP 12
COPY F M
@END
MARK DIAL
VOID M
SEEK -9999
@REP 11
MODI F 10 #DIAL
@END
MULI #DIAL 11 T
FJMP RELEASE
SEEK -9999
MODE
@REP 11
MODI F 10 M
@END
MODE
;HANGUP
COPY -1 #DIAL
MARK RELEASE
COPY 0 M
SEEK F
MARK NEXTNUMBER
SEEK -1
TEST F < 0
SEEK -1
FJMP NEXTNUMBER
ADDI F 2 T
SEEK -1
SUBI T X T
FJMP ROLLOVER
COPY T F
TEST T = 1
TJMP ODDROLLOVER
COPY 0 X
JUMP DIAL
MARK ROLLOVER
COPY 1 X
COPY -10 F
SEEK -1
JUMP NEXTNUMBER
MARK ODDROLLOVER
COPY 1 X
SEEK -1
COPY -9 F
SEEK -1
JUMP NEXTNUMBER
code:
;XB GLOBAL
GRAB 301
COPY 8 T
MARK LP
@REP 11
COPY M F
@END
SUBI T 1 T
TJMP LP
DROP
LINK 800
KILL
KILL
GRAB 300
WIPE
GRAB 400
WIPE